package com.semis.business.base.service.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.ZipUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.semis.business.base.domain.BasGismsg;
import com.semis.business.base.domain.BasLandSystem;
import com.semis.business.base.mapper.BasLandSystemMapper;
import com.semis.business.base.service.IBasGismsgService;
import com.semis.business.base.service.IBasLandSystemService;
import com.semis.common.constant.HttpStatus;
import com.semis.common.constant.ServerConstants;
import com.semis.common.core.domain.ListSelect;
import com.semis.common.core.domain.entity.SysUser;
import com.semis.business.base.domain.BasLandMsg;
import com.semis.common.exception.ServiceException;
import com.semis.common.utils.GeoToolsUtils;
import com.semis.common.utils.StringUtils;
import com.semis.common.utils.bean.BeanValidators;
import com.semis.common.utils.file.FileUploadUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.validation.Validator;
import java.io.File;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 地块系统数据Service业务层处理
 *
 * @author litu
 * @date 2023-07-17
 */
@Service
public class BasLandSystemServiceImpl extends ServiceImpl<BasLandSystemMapper, BasLandSystem> implements IBasLandSystemService {

    @Autowired
    private BasLandSystemMapper basLandSystemMapper;

    @Autowired
    protected Validator validator;

    @Autowired
    private IBasGismsgService basGismsgService;

    /**
     * 查询地块系统数据列表
     *
     * @param basLandSystem 地块系统数据
     * @param user          用户信息
     * @return 地块系统数据
     */
    @Override
    public List<BasLandSystem> selectBasLandSystemList(BasLandSystem basLandSystem, SysUser user) {
        List<BasLandSystem> basLandSystemList = basLandSystemMapper.selectList(buildQueryWrapper(basLandSystem, user));
        return basLandSystemList;
    }

    /**
     * 查询地块系统数据列表（不分页）
     *
     * @return 地块系统数据集合
     */
    @Override
    public List<ListSelect> selectBasLandSystemSelect(BasLandSystem basLandSystem, SysUser user) {
        LambdaQueryWrapper<BasLandSystem> queryWrapper = buildQueryWrapper(basLandSystem, user);
        // 限制只获取前50条
        queryWrapper.last("limit 0,50");
        List<BasLandSystem> landList = basLandSystemMapper.selectList(queryWrapper);

        // 如果当前没有ID信息，则不需要例会是否检索结果中存在指定值
        Boolean hasNow = StringUtils.isEmpty(basLandSystem.getId()) ? true : false;

        List<ListSelect> selects = new ArrayList<>();
        for (BasLandSystem land : landList) {
            // hasnow为false，表示需要确认是否已经存在指定值
            if (!hasNow) {
                // 特别说明，这里用id传当前值的code信息，避免与code检索冲突。 其他系统与地块系统产生关联依靠地块编码,并非通过ID
                hasNow = land.getCode().equals(basLandSystem.getId());
            }
            // 组装返回数据
            ListSelect temp = new ListSelect(land.getCode(), land.getName());
            temp.setField1(land.getFranchisee());
            temp.setField1(land.getCreditCode());
            selects.add(temp);
        }

        // 遍历之后发现没有需要的指定值，则获取后插入到返回结果中, 主要是前端的下拉选择赋值考虑
        if (!hasNow) {
            LambdaQueryWrapper<BasLandSystem> nowlqw = Wrappers.lambdaQuery();
            nowlqw.eq(BasLandSystem::getCode, basLandSystem.getId());
            nowlqw.eq(BasLandSystem::getDelFlag, ServerConstants.OK);
            BasLandSystem nowLand = basLandSystemMapper.selectOne(nowlqw);

            if (StringUtils.isNotNull(nowLand)) {
                ListSelect now = new ListSelect(nowLand.getId(), nowLand.getName());
                now.setField1(nowLand.getFranchisee());
                now.setField1(nowLand.getCreditCode());
                selects.add(now);
            }
        }

        return selects;
    }

    private LambdaQueryWrapper<BasLandSystem> buildQueryWrapper(BasLandSystem query, SysUser user) {
        Map<String, Object> params = query.getParams();
        LambdaQueryWrapper<BasLandSystem> lqw = Wrappers.lambdaQuery();

        lqw.like(StringUtils.isNotBlank(query.getProvince()), BasLandSystem::getProvince, query.getProvince());
        lqw.like(StringUtils.isNotBlank(query.getCity()), BasLandSystem::getCity, query.getCity());
        lqw.eq(StringUtils.isNotBlank(query.getDistrict()), BasLandSystem::getDistrict, query.getDistrict());
        lqw.like(StringUtils.isNotBlank(query.getName()), BasLandSystem::getName, query.getName());
        lqw.like(StringUtils.isNotBlank(query.getCode()), BasLandSystem::getCode, query.getCode());
        lqw.eq(StringUtils.isNotBlank(query.getIsExceeding()), BasLandSystem::getIsExceeding, query.getIsExceeding());
        lqw.eq(StringUtils.isNotBlank(query.getIsDirectory()), BasLandSystem::getIsDirectory, query.getIsDirectory());
        lqw.eq(StringUtils.isNotBlank(query.getIsInventory()), BasLandSystem::getIsInventory, query.getIsInventory());
        lqw.eq(StringUtils.isNotBlank(query.getIsReview()), BasLandSystem::getIsReview, query.getIsReview());
        lqw.eq(query.getReviewTime() != null, BasLandSystem::getReviewTime, query.getReviewTime());
        lqw.eq(StringUtils.isNotBlank(query.getIsRiskReview()), BasLandSystem::getIsRiskReview, query.getIsRiskReview());
        lqw.eq(StringUtils.isNotBlank(query.getIsEffectReview()), BasLandSystem::getIsEffectReview, query.getIsEffectReview());
        lqw.like(StringUtils.isNotBlank(query.getLocation()), BasLandSystem::getLocation, query.getLocation());
        lqw.like(StringUtils.isNotBlank(query.getIndustry()), BasLandSystem::getIndustry, query.getIndustry());
        lqw.like(StringUtils.isNotBlank(query.getFranchisee()), BasLandSystem::getFranchisee, query.getFranchisee());
        lqw.like(StringUtils.isNotBlank(query.getCreditCode()), BasLandSystem::getCreditCode, query.getCreditCode());
        // 只查询正常的地块
        lqw.eq(BasLandSystem::getDelFlag, ServerConstants.OK);
        lqw.orderByDesc(BasLandSystem::getCode);
        return lqw;
    }


    /**
     * 根据地块编码获取地块的全部信息
     *
     * @param code 地块编码
     * @return
     */
    @Override
    public BasLandSystem getByCode(String code) {
        LambdaQueryWrapper<BasLandSystem> nowlqw = Wrappers.lambdaQuery();
        nowlqw.eq(BasLandSystem::getCode, code);
        nowlqw.eq(BasLandSystem::getDelFlag, ServerConstants.OK);
        BasLandSystem nowLand = basLandSystemMapper.selectOne(nowlqw);
        if (nowLand == null) {
            return null;
        }
        return nowLand;
    }

    /**
     * 导入地块系统数据
     *
     * @param landList        地块系统数据列表
     * @param isUpdateSupport 是否更新支持，如果已存在，则进行更新数据
     * @param operName        操作用户
     * @return 结果
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public String importLand(List<BasLandSystem> landList, Boolean isUpdateSupport, String operName) {
        if (StringUtils.isNull(landList) || landList.size() == 0) {
            throw new ServiceException("导入地块系统数据不能为空！");
        }
        int successNum = 0;
        int failureNum = 0;
        StringBuilder successMsg = new StringBuilder();
        StringBuilder failureMsg = new StringBuilder();
        // 查出所有地块系统数据的编码集合（包含删除状态的）
        LambdaQueryWrapper<BasLandSystem> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.select(BasLandSystem::getCode);
        queryWrapper.eq(BasLandSystem::getDelFlag, ServerConstants.OK);
        List<BasLandSystem> basLandSystemLists = list(queryWrapper);
        List landCodeLists = basLandSystemLists.stream().map(BasLandSystem::getCode).collect(Collectors.toList());
        // 遍历导入数据
        for (BasLandSystem land : landList) {
            try {
                // 验证是否存在这个地块
                BasLandSystem l = basLandSystemMapper.selectLandByCode(land.getCode());
                // 如果不存在则新增
                if (StringUtils.isNull(l)) {
                    BeanValidators.validateWithException(validator, land);
                    land.setDelFlag(ServerConstants.OK);
                    basLandSystemMapper.insert(land);
                    successNum++;
                    successMsg.append("<br/>" + successNum + "、地块 " + land.getName() + "，地块编码 " + land.getCode() + " 导入成功");
                    //  存在则更新
                } else if (isUpdateSupport) {
                    // 编码集合将导入的地块编码去除
                    landCodeLists.remove(land.getCode());
                    BeanValidators.validateWithException(validator, land);
                    land.setId(l.getId());
                    land.setDelFlag(ServerConstants.OK);
                    basLandSystemMapper.updateById(land);
                    successNum++;
                    successMsg.append("<br/>" + successNum + "、地块 " + land.getName() + "，地块编码 " + land.getCode() + " 更新成功");
                } else {
                    // 编码集合将导入的地块编码去除
                    landCodeLists.remove(land.getCode());
                    failureNum++;
                    failureMsg.append("<br/>" + failureNum + "、地块 " + land.getName() + "，地块编码 " + land.getCode() + " 已存在");
                }
            } catch (Exception e) {
                failureNum++;
                String msg = "<br/>" + failureNum + "、地块 " + land.getName() + "，地块编码 " + land.getCode() + " 导入失败：";
                failureMsg.append(msg + e.getMessage());
                log.error(msg, e);
            }
        }
        // 如果存在失败，则返回失败信息
        if (failureNum > 0) {
            failureMsg.insert(0, "数据导入失败！共 " + failureNum + " 条数据格式不正确，错误如下：");
            throw new ServiceException(failureMsg.toString());
        } else {
            // 逻辑删除地块系统数据
            if (landCodeLists.size() > 0) {
                LambdaUpdateWrapper<BasLandSystem> updateWrapper = Wrappers.lambdaUpdate();
                updateWrapper.in(BasLandSystem::getCode, landCodeLists);
                updateWrapper.set(BasLandSystem::getDelFlag, ServerConstants.DELETED);
                update(updateWrapper);
            }
            successMsg.insert(0, "数据导入成功！共 " + successNum + " 条，数据如下：");
        }
        return successMsg.toString();
    }

    /**
     * 批量上传地块系统数据地理信息shp文件
     *
     * @param fileName shp文件的zip文件路径
     * @return
     */
    @Override
    public String batchUploadLandShpFile(String fileName) {
        String filePath = FileUploadUtils.getLocalTempFilePath(fileName);
        if (!FileUtil.exist(filePath)) {
            throw new ServiceException("压缩包文件不存在！", HttpStatus.WARN);
        }
        // 解压地块系统数据地理信息shp文件ZIP压缩包
        File fileFolder = ZipUtil.unzip(filePath, StringUtils.removeEnd(filePath, ".zip"), Charset.forName("GBK"));

        // 循环解析单个shp
        List<BasGismsg> basGismsgList = new ArrayList<>();
        List<String> JsonResult = new ArrayList<>();
        List<String> codeList = new ArrayList<>();
        List<File> allZipFiles = FileUtil.loopFiles(fileFolder.getPath());
        for (int i = 0; i < allZipFiles.size(); i++) {
            File landzip = allZipFiles.get(i);
            String code = FileNameUtil.getPrefix(landzip);
            codeList.add(code);

            try {
                // 处理单个json文件
                String geoJson = GeoToolsUtils.transformShpZipToGeoJson(landzip.getAbsolutePath());

                // 创建土地GIS信息
                BasGismsg basGismsg = new BasGismsg();
                basGismsg.setLandCode(code);
                basGismsg.setGeoJson(geoJson);
                basGismsgList.add(basGismsg);

                JsonResult.add("文件：" + FileNameUtil.getName(landzip.getName()) + "，解析成功；<br />");
            } catch (Exception e) {
                JsonResult.add("文件：" + FileNameUtil.getName(landzip.getName()) + "，解析失败；<br />");
            }
        }

        // 保存前先删除旧的土地GIS信息
        LambdaQueryWrapper<BasGismsg> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.in(BasGismsg::getLandCode, codeList);
        basGismsgService.remove(queryWrapper);
        basGismsgService.saveBatch(basGismsgList);

        // 删除解压文件
        FileUtil.del(fileFolder);
        FileUtil.del(filePath);

        return StringUtils.join(JsonResult);
    }

    /**
     * 上传地块系统数据地理信息shp文件
     *
     * @param fileName shp文件路径
     * @param code     地块编码
     * @return
     */
    @Override
    public String uploadLandShpFile(String fileName, String code) {
        String filePath = FileUploadUtils.getLocalTempFilePath(fileName);
        String geoJson = GeoToolsUtils.transformShpZipToGeoJson(filePath);

        // shp文件转geoJson字符串（shp文件坐标系必须为EPSG:WGS 84）
        // 保存前先删除旧的土地GIS信息
        LambdaQueryWrapper<BasGismsg> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(BasGismsg::getLandCode, code);
        basGismsgService.remove(queryWrapper);
        // 创建土地GIS信息
        BasGismsg basGismsg = new BasGismsg();
        basGismsg.setLandCode(code);
        basGismsg.setGeoJson(geoJson);
        basGismsgService.save(basGismsg);

        // 删除解压文件
        FileUtil.del(filePath);

        return geoJson;
    }

    /**
     * 上传地块系统数据地理信息shp文件并匹配
     *
     * @param fileName shp文件web路径
     * @param code     地块编码
     * @return
     */
    @Override
    public Map matchLandShpFile(String fileName, String code) {
        String filePath = FileUploadUtils.getLocalTempFilePath(fileName);
        String geoJson = GeoToolsUtils.transformShpZipToGeoJson(filePath);

        // shp文件转geoJson字符串（shp文件坐标系必须为EPSG:WGS 84）
        // 判断传入的地块编码有无geoJson数据
        LambdaQueryWrapper<BasGismsg> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(BasGismsg::getLandCode, code);
        BasGismsg basGismsg = basGismsgService.getOne(queryWrapper);
        // 删除解压文件
        FileUtil.del(filePath);
        Map resultMap = new HashMap();
        if (basGismsg != null) {
            basGismsg.setRemark("已匹配");
            basGismsgService.updateById(basGismsg);
            resultMap.put("coordinates", "已匹配");
        } else {
            // 返回GeoJson的第一个坐标
            JSONObject geoJsonObj = JSONObject.parseObject(geoJson);
            JSONArray coordinates = geoJsonObj.getJSONArray("features").getJSONObject(0)
                    .getJSONObject("geometry").getJSONArray("coordinates").getJSONArray(0).getJSONArray(0);
            resultMap.put("coordinates", coordinates.get(0));
            resultMap.put("geoJson", geoJson);
        }
        return resultMap;
    }

    /**
     * 获取坐标点周围的地块系统数据
     *
     * @param longitude 中心经度
     * @param latitude  中心纬度
     * @param extent    范围长度（千米）
     * @return 周围的地块数据
     */
    @Override
    public List<JSONObject> getSurroundingLand(Float longitude, Float latitude, Integer extent) {
        Float longitudeScale = 0.011F;// 一千米大致经度
        Float latitudeScale = 0.009F;// 一千米大致纬度
        Float longitudeMax = longitude + extent * longitudeScale;
        Float longitudeMin = longitude - extent * longitudeScale;
        Float latitudeMax = latitude + extent * latitudeScale;
        Float latitudeMin = latitude - extent * latitudeScale;
        List<BasLandMsg> basLandMsgs = basLandSystemMapper.getSurroundingLand(longitude, latitude, longitudeMax, longitudeMin, latitudeMax, latitudeMin);

        if (basLandMsgs == null || basLandMsgs.isEmpty()) {
            return null;
        }

        List<JSONObject> allLandMsgs = new ArrayList<>();
        for (BasLandMsg basLandMsg : basLandMsgs) {
            JSONObject landJson = JSONObject.parseObject(basLandMsg.getGeoJson());

            // 构造图形信息
            JSONObject landPros = new JSONObject();
            landPros.put("id", basLandMsg.getId());
            landPros.put("name", basLandMsg.getName());
            landPros.put("code", basLandMsg.getCode());
            landPros.put("district", basLandMsg.getDistrict());
            landPros.put("landArea", basLandMsg.getLandArea());
            landPros.put("location", basLandMsg.getLocation());
            landPros.put("longitude", basLandMsg.getLongitude());
            landPros.put("latitude", basLandMsg.getLatitude());
            landPros.put("industry", basLandMsg.getIndustry());
            landPros.put("isExceeding", basLandMsg.getIsExceeding());
            landPros.put("remark", basLandMsg.getRemark());

            // 处理集合和单个要素
            if ("FeatureCollection".equals(landJson.getString("type"))) {
                JSONArray features = landJson.getJSONArray("features");
                for (int i = 0; i < features.size(); i++) {
                    JSONObject feature = features.getJSONObject(i);
                    feature.put("id", basLandMsg.getId() + i);
                    feature.put("properties", landPros);
                    allLandMsgs.add(feature);
                }
            } else {
                landJson.put("id", basLandMsg.getId());
                landJson.put("properties", landPros);
                allLandMsgs.add(landJson);
            }
        }

        return allLandMsgs;
    }

}
